#!/bin/sh

if [ -f /tmp/tor.firewall.running ] ; then
	exit
fi
touch /tmp/tor.firewall.running


# constants for tor bandwidth monitoring rules

backup_script_dir="/tmp/bw_backup"
backup_script="$backup_script_dir/do_tor_bw_backup.sh"
tmp_cron="/tmp/tmp.tor.cron"


minute_s=60
hour_s=3600
day_s=86400

#high res intervals
hr1_interval=2
hr1_num_intervals=449
hr1_reset_time=2
	
hr2_interval="minute"
hr2_num_intervals=359
	
hr3_interval=$((3*$minute_s))
hr3_num_intervals=479
hr3_reset_time=$((3*$minute_s))
	
hr4_interval=$((2*$hour_s))
hr4_num_intervals=359
hr4_reset_time=$((2*$hour_s))

hr5_interval="day"
hr5_num_intervals=365

#low res intervals
lr1_interval="minute"
lr1_num_intervals=15

lr2_interval=$((15*$minute_s))
lr2_num_intervals=24
lr2_reset_time=$((15*$minute_s))
	
lr3_interval="hour"
lr3_num_intervals=24

lr4_interval="day"
lr4_num_intervals=31

lr5_interval="month"
lr5_num_intervals=12


mon_nums="1 2 3 4 5"
bw_ids=""




bw_restore()
{
	bw_id="$1"
	backup_to_tmp="$2"
	
	if [ -e "/usr/data/bwmon/$bw_id.bw" ] ; then
		bw_set -i "$bw_id" -h -f /usr/data/bwmon/$bw_id.bw >/dev/null 2>&1
	elif [ -e "/tmp/data/bwmon/$bw_id.bw" ] ; then
		bw_set -i "$bw_id" -h -f /tmp/data/bwmon/$bw_id.bw >/dev/null 2>&1
	elif [ -e "/usr/data/bwmon/$bw_id" ] ; then
		bw_convert "/usr/data/bwmon/$bw_id" "/usr/data/bwmon/$bw_id.bw"
		rm "/usr/data/bwmon/$bw_id"
		bw_set -i "$bw_id" -h -f /usr/data/bwmon/$bw_id.bw >/dev/null 2>&1
	elif [ -e "/tmp/data/bwmon/$bw_id" ] ; then
		bw_convert "/tmp/data/bwmon/$bw_id" "/usr/data/bwmon/$bw_id.bw"
		rm "/tmp/data/bwmon/$bw_id"
		bw_set -i "$bw_id" -h -f /tmp/data/bwmon/$bw_id.bw >/dev/null 2>&1
	fi

	if [ -e "$tmp_cron" ] ; then
		if [ "$backup_to_tmp" = "1" ] ; then
			echo "bw_get -i \"$bw_id\" -h -f \"/tmp/data/bwmon/$bw_id.bw\" >/dev/null 2>&1" >> "$backup_script"
		else
			echo "bw_get -i \"$bw_id\" -h -f \"/usr/data/bwmon/$bw_id.bw\" >/dev/null 2>&1" >> "$backup_script"
		fi
	fi
}

update_cron()
{
	old_md5=$(md5sum /etc/crontabs/root)
	old_md5=${old_md5% *}
	new_md5=$(md5sum "$tmp_cron")
	new_md5=${new_md5% *}
	if [ "$old_md5" = "$new_md5" ] ; then
		rm "$tmp_cron"
	else
		mv "$tmp_cron" /etc/crontabs/root
		if pidof crond > /dev/null 2>&1; then
			/etc/init.d/cron restart
		fi
	fi
}


clear_chains()
{
	delete_chain_from_table nat    tor_client
	delete_chain_from_table filter tor_client
	delete_chain_from_table filter tor_relay
	
	delete_chain_from_table mangle tor_down_bw
	delete_chain_from_table mangle tor_up_bw

}

initialize()
{

	enabled=$( uci get tor.global.enabled 2>/dev/null )
	client_mode=$( uci get tor.client.client_mode 2>/dev/null )
	relay_mode=$( uci get tor.relay.relay_mode 2>/dev/null )
	mem_disabled=$( uci get tor.global.disabled_due_to_memory 2>/dev/null )
	if  [ "$mem_disabled" = "1" ] ; then
		sleep 3
		enabled=$( uci get tor.global.enabled 2>/dev/null )
	fi
	
	if [ -z "$enabled" ] ; then
		enabled = "0"
	fi
	
	if [ "$enabled" != "0" ] && [ "$client_mode" != "0" ] ; then
		
	
		enabled_ip_file=$( uci get tor.client.enabled_ip_file 2>/dev/null)
		dns_port=$( uci get tor.client.dns_port 2>/dev/null )
		trans_port=$( uci get tor.client.trans_port 2>/dev/null )
		zone=$( uci get tor.client.zone 2>/dev/null )
		block_unsupported_proto=$( uci get tor.client.block_unsupported_proto 2>/dev/null )
		if [ -z "$zone" ] ; then 
			zone="lan"
		fi
		zone_ip=$(uci -P /var/state get network.$zone.ipaddr 2>/dev/null )
		zone_mask=$(uci -P /var/state get network.$zone.netmask 2>/dev/null )
		
		hidden_service_subnet=$( uci get tor.client.hidden_service_subnet 2>/dev/null )
		hidden_service_mask_bits=$( uci get tor.client.hidden_service_mask_bits 2>/dev/null )
		if [ -z "$hidden_service_subnet" ] || [ -z "$hidden_service_mask_bits" ] ; then
			hidden_service_subnet="10.192.0.0"
			hidden_service_mask_bits="12"
			uci set tor.client.hidden_service_subnet="$hidden_service_subnet"
			uci set tor.client.hidden_service_mask_bits="$hidden_service_mask_bits"
			uci commit
		fi
	
	
		# setup client mode firewall rules
		iptables -t nat    -N tor_client
		iptables -t filter -N tor_client
	

		if [ "$client_mode" = "3" ] || [ "$client_mode" = "2" ] ; then
			iptables -t nat -A tor_client -p udp --dport 53  -m string --hex-string '|056f6e696f6e00|' --algo bm -j REDIRECT --to-ports $dns_port
			iptables -t nat -A tor_client -p tcp --dport 53  -m string --hex-string '|056f6e696f6e00|' --algo bm -j REDIRECT --to-ports $dns_port
			iptables -t nat -A tor_client -p tcp ! --dport 53 -d $hidden_service_subnet/$hidden_service_mask_bits  -j REDIRECT --to-ports $trans_port 
		fi

		test_tor_active=""
		if [ "$client_mode" = "2" ] ; then
		
			if [ -n "$enabled_ip_file" ] ; then
				touch "$enabled_ip_file" 
			fi
		
			ipset --destroy tor_active_ips >/dev/null 2>&1
			ipset --create  tor_active_ips iphash
			for ip in $(cat $enabled_ip_file) ; do ipset --add tor_active_ips $ip ; done
			
			iptables -t nat -A tor_client -m set ! --match-set tor_active_ips src -j RETURN
			test_tor_active=" -m set --match-set tor_active_ips src "
		fi
	
	
	
		if [ "$client_mode" = "1" ] || [ "$client_mode" = "2" ] ; then
	
			# dns rules
			iptables -t nat -A tor_client -p udp   --dport 53 -j REDIRECT --to-ports $dns_port
			iptables -t nat -A tor_client -p tcp   --dport 53 -j REDIRECT --to-ports $dns_port
		
			# don't redirect local addresses
			if [ -n "$zone_ip" ] && [ -n "$zone_mask" ] ; then
				iptables -t nat    -A tor_client -d $zone_ip/$zone_mask -j RETURN
				iptables -t filter -A tor_client -d $zone_ip/$zone_mask -j RETURN
			elif [ -n "$zone_ip" ] ; then
				iptables -t nat    -A tor_client -d $zone_ip -j RETURN
				iptables -t filter -A tor_client -d $zone_ip -j RETURN
			fi
			
			# redirect to tor transparent proxy
			iptables -t nat -A tor_client -p tcp ! --dport 53 -j REDIRECT --to-ports $trans_port 
		
			# block udp/icmp if requested
			if [ "$block_unsupported_proto" = "1" ] ; then
				iptables -t filter -A tor_client -p tcp            -j RETURN 
				iptables -t filter -A tor_client -p udp --dport 53 -j RETURN
				iptables -t filter -A tor_client -j REJECT
				iptables -t filter -I zone_${zone}_forward $test_tor_active -j tor_client
			fi
		fi
		iptables -t nat -I zone_${zone}_prerouting -j tor_client
		
	fi
	
	
	
	if [ "$enabled" != "0" ]  && [ "$relay_mode" != "0" ] ; then
		relay_zone=$(uci get tor.relay.zone)
		relay_port=$(uci get tor.relay.relay_port)
		obfsproxy_port=$(uci get tor.relay.obfsproxy_port)
	
		iptables -t filter -N tor_relay
		iptables -t filter -I tor_relay -p tcp --dport $relay_port -j ACCEPT
		if [ "$relay_mode" = "1" ] && [ -n "$obfsproxy_port" ] && [ "$obfsproxy_port" != "0" ] ; then
			iptables -t filter -I tor_relay -p tcp --dport $obfsproxy_port -j ACCEPT
		fi
		iptables -t filter -I input_${relay_zone}_rule -j tor_relay
	fi
	
	if [ "$enabled" != "0" ]  && [ "$relay_mode$client_mode" != "00" ] ; then

		wan_if=$(uci -P /var/state get network.wan.ifname 2>/dev/null)
		if [ -z "$wan_if" ] ; then
			wan_if=$(uci -P /var/state get network.wan.device 2>/dev/null)
		fi
		if [ -n "$wan_if" ] ; then
			
			# create tor bandwidth monitor chains
			iptables -t mangle -N tor_down_bw
			iptables -t mangle -N tor_up_bw

			# first, test if traffic is tor, otherwise return
			# traffic is tor if it's to/from the router itself AND
			# either to the ORPort/obfsproxy port OR to/from an IP that is a known relay
			# an ipset of known relays, "tor_relays" is built by the /usr/sbin/tor_ipset command
			/usr/sbin/update_tor_ipset
			relay_port=$(uci get tor.relay.relay_port)
			obfsproxy_port=$(uci get tor.relay.obfsproxy_port)
			oport=""
			rport=""
			if [ "$relay_mode" != "0" ] ; then rport="$relay_port" ; fi
			if [ "$relay_mode" = "1" ] && [ -n "$obfsproxy_port" ] && [ "$obfsproxy_port" != "0" ] ; then oport="$obfsproxy_port" ; fi

			for p in $rport $oport ; do
				iptables -t mangle -A tor_down_bw -p tcp --dport $p -j CONNMARK --set-mark 0xF0000000/0xF0000000
				iptables -t mangle -A tor_up_bw   -p tcp --sport $p -j CONNMARK --set-mark 0xF0000000/0xF0000000
			done
			iptables -t mangle -A tor_down_bw -m set --match-set tor_relays src -j CONNMARK --set-mark 0xF0000000/0xF0000000
			iptables -t mangle -A tor_up_bw   -m set --match-set tor_relays dst -j CONNMARK --set-mark 0xF0000000/0xF0000000
			iptables -t mangle -A tor_down_bw -m connmark ! --mark 0xF0000000/0xF0000000 -j RETURN
			iptables -t mangle -A tor_up_bw   -m connmark ! --mark 0xF0000000/0xF0000000 -j RETURN


			# add monitoring rules
			for n in $mon_nums ; do
				for res in "hr" "lr" ; do
					interval=$(eval "echo \$$res"$n"_interval")
					num_intervals=$(eval "echo \$$res"$n"_num_intervals")
					reset_time=$(eval "echo \$$res"$n"_reset_time")
					if [ -n "$reset_time" ] ; then reset_time="--reset_time $reset_time" ; fi
	
					iptables -t mangle -A tor_down_bw  -m bandwidth --id "tor-$res$n-download-$interval-$num_intervals" --reset_interval $interval --intervals_to_save $num_intervals $reset_time
					iptables -t mangle -A tor_up_bw    -m bandwidth --id "tor-$res$n-upload-$interval-$num_intervals"   --reset_interval $interval --intervals_to_save $num_intervals $reset_time
					
					next_ids="tor-$res$n-download-$interval-$num_intervals tor-$res$n-upload-$interval-$num_intervals"
					if [ -z "$bw_ids" ] ; then bw_ids="$next_ids" ; else bw_ids="$bw_ids $next_ids" ; fi
				done
			done


			#clear connmark we set
			iptables -t mangle -A tor_down_bw -j CONNMARK --set-mark 0x0/0xF0000000
			iptables -t mangle -A tor_up_bw   -j CONNMARK --set-mark 0x0/0xF0000000


			# create rules to jump to tor bandwidth chain
			iptables -t mangle -A INPUT   -i $wan_if -p tcp -j tor_down_bw
			iptables -t mangle -A OUTPUT  -o $wan_if -p tcp -j tor_up_bw

		
			# enable backups of bandwidth data & rebuilding tor_relays ipset
			touch /etc/crontabs/root
			grep -v "$backup_script" /etc/crontabs/root | grep -v /usr/sbin/update_tor_ipset > "$tmp_cron"
			echo "0 0,4,8,12,16,20 * * * $backup_script" >> "$tmp_cron"
			echo "* * * * * /usr/sbin/update_tor_ipset" >> "$tmp_cron"

		
			mkdir -p "$backup_script_dir"
			echo "#!/bin/sh" > "$backup_script"
			chmod 700 "$backup_script"
		
	
			for i in $bw_ids ; do
				is_hr123=$(echo "$i" | egrep "\-hr\-[123]")
				is_lr123=$(echo "$i" | egrep "\-lr\-[123]")
				if [ -n "$is_hr123" ] || [ -n "$is_lr123" ]   ; then
					bw_restore "$i" 1
				else
					bw_restore "$i" 0
				fi
			done
		
			update_cron

		fi
	fi
}

shutdown()
{
	touch /etc/crontabs/root

	if [ -e "$backup_script" ] ; then
		sh "$backup_script" 2>/dev/null
		rm -rf "$backup_script"
	fi

	grep -v "$backup_script" /etc/crontabs/root | grep -v /usr/sbin/update_tor_ipset > "$tmp_cron"
	update_cron

	clear_chains
}


RUN_MODE="$1"


if [ "$RUN_MODE" != "start" ] && [ "$RUN_MODE" != "stop" ] && [ "$RUN_MODE" != "restart" ] ; then
	RUN_MODE="restart"
fi

if [ "$RUN_MODE" = "start" ] || [ "$RUN_MODE" = "restart" ] ; then
	shutdown
	initialize
elif [ "$RUN_MODE" = "stop" ] ; then
	shutdown
fi

rm /tmp/tor.firewall.running

